﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Relay.UI.Controls.Docking.Primitives;
using System.Diagnostics;
using System.Windows.Controls.Primitives;
using System.Windows.Markup;
using Relay.UI.Controls.Utility;

namespace Relay.UI.Controls.Docking {
    /// <summary>
    /// A window that can be docked into a DockManager.
    /// </summary>
    [ContentProperty("Content")]
    public class DockWindow : Control, IDockHierarchyObject, IDropTarget {
        #region Fields
        private bool _dragging = false;
        private Point _clickPos = new Point();
        #endregion

        #region Dependency Properties
        /// <summary>
        /// Identifies the Title dependency property.
        /// </summary>
        public static readonly DependencyProperty TitleProperty = DependencyProperty.Register("Title", typeof(string),
            typeof(DockWindow));

        /// <summary>
        /// Identifies the Content dependency property.
        /// </summary>
        public static readonly DependencyProperty ContentProperty = DependencyProperty.Register("Content",
            typeof(FrameworkElement), typeof(DockWindow), new FrameworkPropertyMetadata(null,
                new PropertyChangedCallback(OnContentChanged)));

        /// <summary>
        /// Identifies the Image dependency property.
        /// </summary>
        public static readonly DependencyProperty ImageProperty = DependencyProperty.Register("Image",
            typeof(ImageSource), typeof(DockWindow), new FrameworkPropertyMetadata(
                new BitmapImage(new Uri("pack://application:,,,/Relay.UI.Controls;component/Images/defaultimage.png"))));

        /// <summary>
        /// Identifies the IsSelected dependency property.
        /// </summary>
        public static readonly DependencyProperty IsSelectedProperty =
            Selector.IsSelectedProperty.AddOwner(typeof(DockWindow),
            new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault |
                FrameworkPropertyMetadataOptions.AffectsParentMeasure,
                new PropertyChangedCallback(OnIsSelectedChanged)));
        #endregion

        #region Constructor
        /// <summary>
        /// Initializes the <see cref="DockWindow"/> class.
        /// </summary>
        static DockWindow() {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(DockWindow),
                new FrameworkPropertyMetadata(typeof(DockWindow)));

            FocusableProperty.OverrideMetadata(typeof(DockWindow), new FrameworkPropertyMetadata(false));            
        }
        #endregion

        #region Properties
        /// <summary>
        /// Gets or sets the title of this docked window.
        /// </summary>
        /// <value>The title.</value>
        public string Title {
            get { return (string)GetValue(TitleProperty); }
            set { SetValue(TitleProperty, value); }
        }

        /// <summary>
        /// Gets or sets the content of this window.
        /// </summary>
        /// <value>The content.</value>
        public FrameworkElement Content {
            get { return (FrameworkElement)GetValue(ContentProperty); }
            set { SetValue(ContentProperty, value); }
        }

        /// <summary>
        /// Gets or sets a value indicating whether this window is selected.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this window is selected; otherwise, <c>false</c>.
        /// </value>
        public bool IsSelected {
            get { return (bool)GetValue(IsSelectedProperty); }
            set { SetValue(IsSelectedProperty, value); }
        }

        /// <summary>
        /// Gets or sets the image displayed on the tab for this window.
        /// </summary>
        /// <value>The image associated with this window.</value>
        public ImageSource Image {
            get { return (ImageSource)GetValue(ImageProperty); }
            set { SetValue(ImageProperty, value); }
        }

        internal DockWindowGroup ItemsParent {
            get { return ItemsControl.ItemsControlFromItemContainer(this) as DockWindowGroup; }
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Closes this window.
        /// </summary>
        public void Hide() {
            DockWindowGroup parent = Parent as DockWindowGroup;

            if (parent != null) {
                parent.HideWindow(this);
            }
        }

        /// <summary>
        /// Shows this window in its previously cached container tree if one exists.
        /// </summary>
        public void Show() {
            DockWindowGroup parent = Parent as DockWindowGroup;

            if (parent != null)
                parent.Show();

            Visibility = Visibility.Visible;
            IsSelected = true;

            parent.NotifyChildVisibiltyChanged(this);
        }

        /// <summary>
        /// Splits this DockWindow into its own window.
        /// </summary>
        /// <returns>The newly created FloatingWindow.</returns>
        private FloatingWindow CreateFloatingWindow() {
            Size oldFloatSize = ItemsParent.FloatedSize;
            double oldDockSize = ItemsParent.DockedSize;
            DockManager manager = ((IDockHierarchyObject)this).GetDockManager();

            DockWindowGroup grp = new DockWindowGroup();
            grp.BeginInit();

            ItemsParent.Items.Remove(this);
            grp.Items.Add(this);
            grp.DockedSize = oldDockSize;            

            FloatingWindow window = new FloatingWindow(grp, manager);
            window.BeginInit();

            window.Width = oldFloatSize.Width;
            window.Height = oldFloatSize.Height;

            window.EndInit();
            grp.EndInit();
            return window;
        }
        #endregion

        #region Private Methods
        protected virtual void OnContentChanged(UIElement newContent) {
            if (IsSelected) {
                if (ItemsParent != null) {
                    ItemsParent.SelectedContent = newContent;
                }
            }
        }
        #endregion

        #region Event Handlers
        private static void OnIsSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
            DockWindow window = d as DockWindow;
            bool selected = (bool)e.NewValue;

            RoutedEventArgs newArgs = new RoutedEventArgs(selected ? Selector.SelectedEvent
                                                                   : Selector.UnselectedEvent, window);
            window.RaiseEvent(newArgs);
        }

        private static void OnContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
            DockWindow window = d as DockWindow;
            if (e.NewValue != null && !(e.NewValue is UIElement))
                throw new InvalidOperationException("Cannot add non-UIElement content to a DockWindow.");

            window.OnContentChanged(e.NewValue as UIElement);
        }
        #endregion

        #region Overrides
        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) {
            if (!IsSelected || (e.Source == this)) {
                ItemsParent.NotifyItemClicked(this);
            }

            _dragging = true;
            _clickPos = e.GetPosition(this);

            CaptureMouse();

            base.OnMouseLeftButtonDown(e);
        }

        protected override void OnMouseMove(MouseEventArgs e) {
            if (_dragging && e.LeftButton == MouseButtonState.Pressed) {
                Point mousePos = e.GetPosition(this);
                Point screenPos = PointToScreen(mousePos);
                if (Math.Abs(mousePos.X - _clickPos.X) > SystemParameters.MinimumHorizontalDragDistance ||
                    Math.Abs(mousePos.Y - _clickPos.Y) > SystemParameters.MinimumVerticalDragDistance) {
                    Mouse.Capture(null);
                    FloatingWindow window = CreateFloatingWindow();

                    _dragging = false;

                    window.Left = screenPos.X - window.Width / 2;
                    window.Top = screenPos.Y - SystemParameters.SmallCaptionHeight / 2;

                    window.Show();

                    DockManager manager = ((IDockHierarchyObject)this).GetDockManager();
                    manager.DockService.Start(window);
                }
            }

            base.OnMouseMove(e);
        }

        protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) {
            _dragging = false;
            Mouse.Capture(null);

            base.OnMouseLeftButtonUp(e);
        }
        
        /// <summary>
        /// Returns the string representation of a <see cref="T:System.Windows.Controls.Control"/> object.
        /// </summary>
        /// <returns>A string that represents the control.</returns>
        public override string ToString() {
            return Title;
        }
        #endregion

        #region IDockHierarchyObject Members
        DockManager IDockHierarchyObject.GetDockManager() {
            return ((IDockHierarchyObject)ItemsParent).GetDockManager();
        }

        IDropTarget[] IDockHierarchyObject.FindDropTargets() {
            return new IDropTarget[] { };
        }
        #endregion

        #region IDropTarget Members
        private DockHintAdorner _hint = null;
        private bool _leftSide = false;

        private void RemoveHint() {
            if (_hint != null) {
                AdornerLayer layer = VisualTreeHelper.GetParent(_hint) as AdornerLayer;
                if (layer != null)
                    layer.Remove(_hint);
                _hint = null;
            }
        }

        bool IDropTarget.Update(FloatingWindow window, Point mousePos, bool disable) {
            DockWindowGroup parent = Parent as DockWindowGroup;

            if (this.PointInsideElement(mousePos) && parent.HasMultipleVisibleItems) {
                Point internalPos = PointFromScreen(mousePos);
                bool thisLeftSide = (internalPos.X < ActualWidth / 2);

                if ((_hint == null || thisLeftSide != _leftSide) && !disable) {
                    RemoveHint();
                    _leftSide = thisLeftSide;
                    
                    AdornerLayer layer = AdornerLayer.GetAdornerLayer(this);
                    _hint = new DockHintAdorner(this,
                        (_leftSide ? DockOverlayHitTestResult.Left : DockOverlayHitTestResult.Right), ActualWidth / 2);
                    layer.Add(_hint);

                    return true;
                }

                return !disable;
            }

            RemoveHint();
            return false;
        }

        void IDropTarget.End(FloatingWindow window) {
            RemoveHint();
        }

        void IDropTarget.PerformDock(FloatingWindow window, Point mousePos) {
            DockWindow[] windows = window.Panel.GetAllDockWindows();
            DockWindowGroup parent = Parent as DockWindowGroup;

            if (parent == null)
                return; // Should not be possible

            int sourceIndex = parent.Items.IndexOf(this);
            int destIndex = sourceIndex;
            int offset = _leftSide ? 0 : 1;
            foreach (DockWindow w in windows) {
                DockWindowGroup windowParent = w.Parent as DockWindowGroup;

                if (windowParent == null)
                    throw new InvalidOperationException("Source DockWindow does not have a parent somehow");

                windowParent.Items.Remove(w);
                parent.Items.Insert(sourceIndex + offset, w);

                if (_leftSide) offset++;
            }
        }
        #endregion
    }
}
